package org.bndtools.build.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.PrimitiveType.Code;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.ui.IMarkerResolution;
import aQute.bnd.build.Project;
import aQute.bnd.osgi.Processor;
import aQute.service.reporter.Report.Location;
public abstract class AbstractBuildErrorDetailsHandler implements BuildErrorDetailsHandler {
private static final Map<Code,String> PRIMITIVES_TO_SIGNATURES;
static {
Map<Code,String> tmp = new HashMap<PrimitiveType.Code,String>();
tmp.put(PrimitiveType.VOID, "V");
tmp.put(PrimitiveType.BOOLEAN, "Z");
tmp.put(PrimitiveType.BYTE, "B");
tmp.put(PrimitiveType.SHORT, "S");
tmp.put(PrimitiveType.CHAR, "C");
tmp.put(PrimitiveType.INT, "I");
tmp.put(PrimitiveType.FLOAT, "F");
tmp.put(PrimitiveType.LONG, "J");
tmp.put(PrimitiveType.DOUBLE, "D");
PRIMITIVES_TO_SIGNATURES = Collections.unmodifiableMap(tmp);
}
public static final IResource getDefaultResource(IProject project) {
return getDefaultResource(project, Project.BNDFILE);
}
public static final IResource getDefaultResource(IProject project, String name) {
if ((name == null) || name.isEmpty())
return project;
IResource bndFile = project.getFile(name);
if (bndFile.exists())
return bndFile;
return project;
}
/**
* Obtain an AST for a source file in a project
*
* @param javaProject
* @param className
* @return An AST, or null if no source file exists for that class
* @throws JavaModelException
*/
public static final CompilationUnit createAST(IJavaProject javaProject, String className) throws JavaModelException {
IType type = javaProject.findType(className);
if (type == null)
return null;
final ICompilationUnit cunit = type.getCompilationUnit();
if (cunit == null)
return null; // not a source type
@SuppressWarnings("deprecation")
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setSource(cunit);
parser.setResolveBindings(true);
return (CompilationUnit) parser.createAST(null);
}
/**
* Create a marker on a Java Type
*
* @param javaProject
* @param className
* - the fully qualified class name (e.g java.lang.String)
* @param markerAttributes
* @param hasResolutions
* - true if the marker will have resolutions
* @return Marker Data that can be used to create an {@link IMarker}, or null if no location can be found
* @throws JavaModelException
*/
public static final MarkerData createTypeMarkerData(IJavaProject javaProject, final String className, final Map<String,Object> markerAttributes, boolean hasResolutions) throws JavaModelException {
final CompilationUnit ast = createAST(javaProject, className);
if (ast == null)
return null;
ast.accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration typeDecl) {
ITypeBinding typeBinding = typeDecl.resolveBinding();
if (typeBinding != null) {
if (typeBinding.getBinaryName().equals(className)) {
SimpleName nameNode = typeDecl.getName();
markerAttributes.put(IMarker.CHAR_START, nameNode.getStartPosition());
markerAttributes.put(IMarker.CHAR_END, nameNode.getStartPosition() + nameNode.getLength());
return false;
}
}
return true;
}
});
if (!markerAttributes.containsKey(IMarker.CHAR_START))
return null;
return new MarkerData(ast.getJavaElement().getResource(), markerAttributes, hasResolutions);
}
/**
* Create a marker on a Java Method
*
* @param javaProject
* @param className
* - the fully qualified class name (e.g java.lang.String)
* @param methodName
* @param methodSignature
* - signatures are in "internal form" e.g. (Ljava.lang.Integer;[Ljava/lang/String;Z)V
* @param markerAttributes
* - attributes that should be included in the marker, typically a message. The start and end points for
* the marker are added by this method.
* @param hasResolutions
* - true if the marker will have resolutions
* @return Marker Data that can be used to create an {@link IMarker}, or null if no location can be found
* @throws JavaModelException
*/
public static final MarkerData createMethodMarkerData(IJavaProject javaProject, final String className, final String methodName, final String methodSignature, final Map<String,Object> markerAttributes, boolean hasResolutions)
throws JavaModelException {
final CompilationUnit ast = createAST(javaProject, className);
if (ast == null)
return null;
ast.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration methodDecl) {
if (matches(ast, methodDecl, methodName, methodSignature)) {
// Create the marker attribs here
markerAttributes.put(IMarker.CHAR_START, methodDecl.getStartPosition());
markerAttributes.put(IMarker.CHAR_END, methodDecl.getStartPosition() + methodDecl.getLength());
}
return false;
}
private boolean matches(CompilationUnit ast, MethodDeclaration methodDecl, String methodName, String signature) {
if ("<init>".equals(methodName)) {
if (!methodDecl.isConstructor()) {
return false;
}
} else if (!methodDecl.getName().getIdentifier().equals(methodName)) {
return false;
}
return getSignature(ast, methodDecl).equals(signature);
}
private String getSignature(CompilationUnit ast, MethodDeclaration methodDecl) {
StringBuilder signatureBuilder = new StringBuilder("(");
for (@SuppressWarnings("unchecked")
Iterator<SingleVariableDeclaration> it = methodDecl.parameters().iterator(); it.hasNext();) {
SingleVariableDeclaration decl = it.next();
appendType(ast, signatureBuilder, decl.getType(), decl.getExtraDimensions());
}
signatureBuilder.append(")");
appendType(ast, signatureBuilder, methodDecl.getReturnType2(), 0);
return signatureBuilder.toString();
}
private void appendType(CompilationUnit ast, StringBuilder signatureBuilder, Type typeToAdd, int extraDimensions) {
for (int i = 0; i < extraDimensions; i++) {
signatureBuilder.append('[');
}
Type rovingType = typeToAdd;
if (rovingType == null) {
//A special return type for constructors, nice one Eclipse...
signatureBuilder.append("V");
} else {
if (rovingType.isArrayType()) {
ArrayType type = (ArrayType) rovingType;
int depth = type.getDimensions();
for (int i = 0; i < depth; i++) {
signatureBuilder.append('[');
}
//We still need to add the array component type, which might be primitive or a reference
rovingType = type.getElementType();
}
// Type erasure means that we should ignore parameters
if (rovingType.isParameterizedType()) {
rovingType = ((ParameterizedType) rovingType).getType();
}
if (rovingType.isPrimitiveType()) {
PrimitiveType type = (PrimitiveType) rovingType;
signatureBuilder.append(PRIMITIVES_TO_SIGNATURES.get(type.getPrimitiveTypeCode()));
} else if (rovingType.isSimpleType()) {
SimpleType type = (SimpleType) rovingType;
String name;
if (type.getName().isQualifiedName()) {
name = type.getName().getFullyQualifiedName();
} else {
name = getFullyQualifiedNameForSimpleName(ast, type.getName());
}
name = name.replace('.', '/');
signatureBuilder.append("L").append(name).append(";");
} else if (rovingType.isQualifiedType()) {
QualifiedType type = (QualifiedType) rovingType;
String name = type.getQualifier().toString().replace('.', '/') + '/' + type.getName().getFullyQualifiedName().replace('.', '/');
signatureBuilder.append("L").append(name).append(";");
} else {
throw new IllegalArgumentException("We hit an unknown type " + rovingType);
}
}
}
private String getFullyQualifiedNameForSimpleName(CompilationUnit ast, Name typeName) {
String name = typeName.getFullyQualifiedName();
@SuppressWarnings("unchecked")
List<ImportDeclaration> ids = ast.imports();
for (ImportDeclaration id : ids) {
if (id.isStatic())
continue;
if (id.isOnDemand()) {
String packageName = id.getName().getFullyQualifiedName();
try {
if (ast.getJavaElement().getJavaProject().findType(packageName + "." + name) != null) {
name = packageName + '.' + name;
}
} catch (JavaModelException e) {}
} else {
String importName = id.getName().getFullyQualifiedName();
if (importName.endsWith("." + name)) {
name = importName;
break;
}
}
}
if (name.indexOf('.') < 0) {
try {
if (ast.getJavaElement().getJavaProject().findType(name) == null) {
name = "java.lang." + name;
}
} catch (JavaModelException e) {}
}
return name;
}
});
if (!markerAttributes.containsKey(IMarker.CHAR_START))
return null;
return new MarkerData(ast.getJavaElement().getResource(), markerAttributes, hasResolutions);
}
/**
* Create a marker on a Java Method
*
* @param javaProject
* @param className
* - the fully qualified class name (e.g java.lang.String)
* @param methodName
* @param methodSignature
* - signatures are in "internal form" e.g. (Ljava.lang.Integer;[Ljava/lang/String;Z)V
* @param markerAttributes
* - attributes that should be included in the marker, typically a message. The start and end points for
* the marker are added by this method.
* @param hasResolutions
* - true if the marker will have resolutions
* @return Marker Data that can be used to create an {@link IMarker}, or null if no location can be found
* @throws JavaModelException
*/
public static final MarkerData createFieldMarkerData(IJavaProject javaProject, final String className, final String fieldName, final Map<String,Object> markerAttributes, boolean hasResolutions) throws JavaModelException {
final CompilationUnit ast = createAST(javaProject, className);
if (ast == null)
return null;
ast.accept(new ASTVisitor() {
@Override
public boolean visit(FieldDeclaration fieldDecl) {
if (matches(ast, fieldDecl, fieldName)) {
// Create the marker attribs here
markerAttributes.put(IMarker.CHAR_START, fieldDecl.getStartPosition());
markerAttributes.put(IMarker.CHAR_END, fieldDecl.getStartPosition() + fieldDecl.getLength());
}
return false;
}
private boolean matches(@SuppressWarnings("unused") CompilationUnit ast, FieldDeclaration fieldDecl, String fieldName) {
@SuppressWarnings("unchecked")
List<VariableDeclarationFragment> list = (List<VariableDeclarationFragment>) fieldDecl.getStructuralProperty(FieldDeclaration.FRAGMENTS_PROPERTY);
for (VariableDeclarationFragment vdf : list) {
if (fieldName.equals(vdf.getName().toString())) {
return true;
}
}
return false;
}
});
if (!markerAttributes.containsKey(IMarker.CHAR_START))
return null;
return new MarkerData(ast.getJavaElement().getResource(), markerAttributes, hasResolutions);
}
@Override
public List<IMarkerResolution> getResolutions(IMarker marker) {
return Collections.emptyList();
}
@Override
public List<ICompletionProposal> getProposals(IMarker marker) {
return Collections.emptyList();
}
/**
* Bridge method. The actual parameter should have been Processor since we can have Builder, Workspace, Project.
* etc. If it is a project, we defer to the old method. Otherwise we allow others to override this method.
*/
@Override
public List<MarkerData> generateMarkerData(IProject project, Processor model, Location location) throws Exception {
if (model instanceof Project)
return generateMarkerData(project, (Project) model, location);
return Collections.emptyList();
}
@Override
public List<MarkerData> generateMarkerData(IProject project, Project model, Location location) throws Exception {
return Collections.emptyList();
}
}